﻿#region License Information

/* •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————•
   | Copyright Aptillon, Inc.                                                                                        |
   | This file is part of the CKS:API project - cksapi.codeplex.com                                                       |
   | Usage, modification and distribution subject to the license terms available here: http://cksapi.codeplex.com/license |
   |                                                                                                                      |
   | Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,             |
   | either express or implied.                                                                                           |
   |                                                                                                                      |
   | THIS MESSAGE MAY NOT BE LEGALLY REMOVED                                                                              |
   •——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————• */

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using CKS.API.Contracts;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Administration;
using Microsoft.Win32;
using Microsoft.SharePoint;
using CKS.API.Utilities;

namespace CKS.API.Logging
{
    public class CKSFarmLogger : CKSLogger
    {
        #region Constants

        const string EventLogApplicationRegistryKeyPath = @"SYSTEM\CurrentControlSet\services\eventlog\Application";

        #endregion

        #region Fields

        private CodeContract _contract;

        private CKSFarmLoggingService _loggingService = new CKSFarmLoggingService();

        #endregion

        #region Constructors

        public CKSFarmLogger(SPWeb rootWeb, bool shouldDispose)
            : base(rootWeb, shouldDispose) { }

        public CKSFarmLogger() : base() { }

        #endregion

        #region Public Methods

        public override void BeginScope()
        {
            //do anything necessary before writing the first entry in a session
        }

        public override void CatchUnhandledExceptions()
        {
            base.CatchUnhandledExceptions();
        }

        public override void EndScope()
        {
            //do anything necessary after writing the last entry in a session
        }

        public void InitializeFarmEnvironment()
        {
            //(do any Farm-logger specific set up here...typically called once from Feature Receiver)

            //make the logging categories available for configuration in Central Admin
            RegisterLoggingService();
        }

        public void UninitializeFarmEnvironment()
        {
            //(do any Farm-logger specific clean up here...typically called once from Feature Receiver)

            //remove logging categories from Central Admin
            UnRegisterLoggingService();

        }

        #endregion

        #region Protected Methods

        protected override void InitializeSpecificEnvironment()
        {
            //Called from base class InitializeEnvironment to allow us to set up the specific child environment after the common stuff is done

            CreateAdminNotificationList();

        }

        protected override void Log(string message, CKSLoggingCategory category, LoggingSeverity severity)
        {
            //Don't need WriteTrace when using WriteEvent.  WriteEvent also writes to Trace

            bool writeEvent = false;
            TraceSeverity traceSeverity = TraceSeverity.None;
            EventSeverity eventSeverity = EventSeverity.None;

            //Make sure to pull from LOCAL to get the values currently set in Central Admin.  This would retrieve the default values instead, which is not what we want:
            //SPDiagnosticsCategory logCategory = _loggingService.Areas[CKSFarmLoggingService.AreaName].Categories[category.ToString()];

            SPDiagnosticsCategory logCategory = CKSFarmLoggingService.Local.Areas[CKSFarmLoggingService.AreaName].Categories[category.ToString()];

            switch (severity)
            {
                case LoggingSeverity.Information:
                    traceSeverity = TraceSeverity.Verbose;
                    writeEvent = false;
                    break;
                case LoggingSeverity.Timing:
                    traceSeverity = TraceSeverity.Medium;
                    writeEvent = false;
                    break;
                case LoggingSeverity.Warning:
                    traceSeverity = TraceSeverity.Medium;
                    writeEvent = false;
                    break;
                case LoggingSeverity.Error:
                    traceSeverity = TraceSeverity.High;
                    eventSeverity = EventSeverity.Error;
                    writeEvent = true;
                    break;
                case LoggingSeverity.CriticalError:
                    traceSeverity = TraceSeverity.High;
                    eventSeverity = EventSeverity.ErrorCritical;
                    writeEvent = true;

                    break;
            }

            if (writeEvent)
            {
                _loggingService.WriteEvent((ushort)0, logCategory, eventSeverity, message);
            }
            else
            {
                _loggingService.WriteTrace((ushort)0, logCategory, traceSeverity, message);
            }

        }

        protected override void NotifyAdministrator(string alertId, string alertBody)
        {
            //take advantage of what SP offers!  Write an entry to a list that admins have an alert set up on.
            if (Check.ThatIsNotNull(SPContext.Current))
            {
                SPList adminList = SPContext.Current.Web.Lists.TryGetList("AdminNotifications");
                if (Check.ThatIsNotNull(adminList))
                {
                    SPListItem newAlert = adminList.AddItem();
                    newAlert["Title"] = alertId;
                    newAlert["Body"] = alertBody;
                    newAlert.Update();
                }
            }
        }

        protected override void WriteToDeveloperDashboard(string message)
        {
            WriteToDeveloperDashboard(message, LogToDeveloperDashboardDummyMethod);
        }

        protected override void WriteToDeveloperDashboard(string message, Action action, uint maxExecutionTime)
        {
            using (SPMonitoredScope monitoredScope = new SPMonitoredScope(message, maxExecutionTime))
            {
                action();
            }
        }

        protected override void WriteToDeveloperDashboard(string message, Action action)
        {
            using (SPMonitoredScope monitoredScope = new SPMonitoredScope(message))
            {
                action();
            }
        }

        public void Monitor(string message, Action action)
        {

            WriteToDeveloperDashboard(message, action);

        }

        #endregion

        #region Private Methods

        //finalizer has to be here instead of in parent because finalizers are not allowed in O365 (sandbox)
        ~CKSFarmLogger()
        {
            if ((Check.ThatAllAreNotNull(CurrentWeb, SPContext.Current)) && (shouldDispose))
            {
                CurrentWeb.Dispose();
            }
        }

        private void CreateAdminNotificationList()
        {
            if (Check.ThatIsNotNull(SPContext.Current))
            {
                if (Check.ThatIsNull(SPContext.Current.Web.Lists.TryGetList("AdminNotifications")))
                {
                    SPContext.Current.Web.Lists.Add("AdminNotifications", "", SPListTemplateType.GenericList);
                    SPList newList = SPContext.Current.Web.Lists.TryGetList("AdminNotifications");
                    if (Check.ThatIsNotNull(SPContext.Current.Web.Lists.TryGetList("AdminNotifications")))
                    {
                        newList.Fields.Add("Body", SPFieldType.Note, true);
                        newList.Update();
                    }
                }
            }
        }

        private void LogToDeveloperDashboardDummyMethod()
        {
            //do nothing...this is an empty method to allow us to write arbitrary messages to the Developer Dashboard whenever we want to.

        }

        void RegisterLoggingService()
        {
            CKSFarmLoggingService tempService = CKSFarmLoggingService.Local;
            if (null != tempService)
            {
                tempService.Update();
            }
            else
            {
                tempService = new CKSFarmLoggingService();
                tempService.Update();

                if (tempService.Status != SPObjectStatus.Online)
                    tempService.Provision();

            }
            try
            {
                foreach (SPServer server in SPFarm.Local.Servers)
                {
                    RegistryKey baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine,
                                            server.Address);

                    if (baseKey != null)
                    {
                        RegistryKey eventLogKey = baseKey.OpenSubKey(EventLogApplicationRegistryKeyPath,
                                                    true);

                        if (eventLogKey != null)
                        {
                            RegistryKey loggingServiceKey = eventLogKey.OpenSubKey(CKSFarmLoggingService.AreaName);

                            if (loggingServiceKey == null)
                            {
                                loggingServiceKey = eventLogKey.CreateSubKey(CKSFarmLoggingService.AreaName,
                                                     RegistryKeyPermissionCheck.ReadWriteSubTree);
                                loggingServiceKey.SetValue("EventMessageFile",
                                  @"C:\Windows\Microsoft.NET\Framework\v2.0.50727\EventLogMessages.dll",
                                  RegistryValueKind.String);
                            }
                        }
                    }
                }
            }
            catch
            {
                //swallow error to avoid a loop
            }

        }

        void UnRegisterLoggingService()
        {
            try
            {

                SPFarm farm = SPFarm.Local;

                if (farm != null)
                {
                    CKSFarmLoggingService tempService = CKSFarmLoggingService.Local;
                    if (Check.ThatIsNotNull(tempService))
                    {
                        tempService.Delete();
                        tempService.Unprovision();
                        tempService.Uncache();
                    }
                    foreach (SPServer server in farm.Servers)
                    {
                        RegistryKey baseKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine,
                                                server.Address);

                        if (baseKey != null)
                        {
                            RegistryKey eventLogKey = baseKey.OpenSubKey(EventLogApplicationRegistryKeyPath,
                                                        true);

                            if (eventLogKey != null)
                            {
                                RegistryKey loggingServiceKey = eventLogKey.OpenSubKey(CKSFarmLoggingService.AreaName);

                                if (loggingServiceKey != null)
                                    eventLogKey.DeleteSubKey(CKSFarmLoggingService.AreaName);
                            }
                        }
                    }
                }
            }
            catch
            {
                //swallow error to avoid a loop
            }
        }

        #endregion

    }
}
